package com.kim.common.utilcom.exception;

import com.kim.common.utilcom.constants.CodeEnum;
import com.kim.common.utilcom.model.Result;
import feign.RetryableException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 全局异常处理
 */
@Slf4j
@ControllerAdvice
@SuppressWarnings("all")
public class GlobalControllerExceptionHandler {

    /**
     * 系统异常处理
     * @param e 异常对象
     * @see ExceptionHandler
     */
    @ExceptionHandler({Exception.class, Throwable.class})
    @ResponseBody
    public Result errorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) {
        Result result = Result.failed(CodeEnum.ERROR.getRetCode(), CodeEnum.ERROR.getRetMsg() + e.getMessage());
        log.error(result.toJsonString(), e);
        // 获取请求URI
        this.getRequestURI(request);
        return result;
    }

    /**
     * 运行时异常处理
     * @param e 异常对象
     * @see ExceptionHandler
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public Result runtimeExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) {
        Result result = Result.failed(CodeEnum.ERROR.getRetCode(), CodeEnum.ERROR.getRetMsg() + e.getMessage());
        log.error(result.toJsonString(), e);
        // 获取请求URI
        this.getRequestURI(request);
        return result;
    }

    /**
     * 请求超时异常处理
     * @param e 异常对象
     * @see ExceptionHandler
     */
    @ExceptionHandler(RetryableException.class)
    @ResponseBody
    public Result retryableExceptionHandler(HttpServletRequest request, RetryableException e) {
        Result result = Result.failed(CodeEnum.REQUEST_TIMED_OUT_ERROR.getRetCode(), CodeEnum.REQUEST_TIMED_OUT_ERROR.getRetMsg() + e.getMessage());
        log.error(result.toJsonString(), e);
        // 获取请求URI
        this.getRequestURI(request);
        return result;
    }

    /**
     * 参数校验异常
     * <p> 表单提交时发生的参数绑定一场</p>
     *
     * @param e 异常对象
     * @return Result   返回类型
     * @throws
     * @see javax.validation.constraints.NotBlank
     * @see javax.validation.constraints.NotNull
     */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Result errorBindHandler(BindException e) {
        log.error("BindException -> 参数校验异常", e);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 参数校验(Valid)异常，将校验失败的所有异常组合成一条错误信息
     *
     * <p> 表单提交时Content-Type为 “application / x-www-form-urlencoded”。
     * 因此，Spring将数据解释为Web表单数据（而不是JSON）。 Spring使用FormHttpMessageConverter将POST主体转换为域对象，并导致BindException。
     * 我们想要的是Spring将POST数据视为JSON，并使用MappingJackson2HttpMessageConverter将POST正文解析为对象。
     * </p>
     *
     * @param e 异常对象
     * @return 异常结果
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException -> 参数绑定校验异常", e);
        // 获取请求URI
        this.getRequestURI(request);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 系统自定义异常处理
     *
     * @param e 异常对象
     * @return BaseHttpParamsResp    返回类型
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Result customErrorHandler(HttpServletRequest request, HttpServletResponse response, BusinessException e) {
        Result result = Result.failed(CodeEnum.ERROR.getRetCode(), e.getLocalizedMessage());
        log.error(result.toJsonString(), e);
        // 获取请求URI
        this.getRequestURI(request);
        return result;
    }

    /**
     * 微服务间调用返回异常给上级
     *
     * @param request  请求
     * @param response 响应
     * @param e        异常对象
     */
    @ExceptionHandler(MicroServiceCallException.class)
    @ResponseBody
    public Result microServiceCallErrorHandler(HttpServletRequest request, HttpServletResponse response, MicroServiceCallException e) {
        Result result = Result.failed(e.getErrorCode(), e.getLocalizedMessage());
        log.error(result.toJsonString(), e);
        // 获取请求URI
        this.getRequestURI(request);
        return result;
    }

    /**
     * 系统自定义异常处理
     *
     * @param e 异常对象
     * @return org.surge.oauth2common.model.Result
     */
    @ExceptionHandler(BusinessCommonException.class)
    @ResponseBody
    public Result businessCommonExceptionHandler(HttpServletRequest request, HttpServletResponse response, BusinessCommonException e) {
        Result result = Result.failed(e.getErrorCode(), e.getAdditionMessage());
        log.error(result.toJsonString(), e);
        // 获取请求URI
        this.getRequestURI(request);
        return result;
    }

    /**
     * IllegalArgumentException（非法参数）异常处理返回json 状态码:400
     *
     * @param exception 异常对象
     * @return com.bccv.nmg.result.Result
     */
    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Result badRequestExceptionHandler(HttpServletRequest request, IllegalArgumentException exception) {
        log.error("IllegalArgumentException -> 400", exception);
        // 获取请求URI
        this.getRequestURI(request);
        return Result.failed(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
    }


    /**
     * AccessDeniedException异常处理返回json 状态码:401 403
     *
     * @param exception 异常对象
     * @return com.bccv.nmg.result.Result
     * @Description
     */
    @ExceptionHandler({AccessDeniedException.class})
    public void badMethodExpressExceptionHandler(HttpServletRequest request, AccessDeniedException exception) {
        log.error("AccessDeniedException -> 403", exception);
        // 获取请求URI
        this.getRequestURI(request);
        throw exception;
    }


    /**
     * 获取请求URI
     */
    private void getRequestURI(HttpServletRequest request) {
        // 获取请求路径
        String requestUrl = request.getRequestURI();
        log.error("获取请求URI -> {}", requestUrl);
    }

    /**
     * 包装绑定异常结果
     * @param bindingResult 绑定结果
     * @return Result 异常结果
     */
    private Result wrapperBindingResult(BindingResult bindingResult) {
        // 构建返回消息
        StringBuilder msg = new StringBuilder();
        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
        }
        return Result.failed(CodeEnum.ERROR.getRetCode(), msg.substring(2));
    }
}
